#!/usr/bin/env python
# -*- coding: utf-8 -*-


import sys
import os
import time
import atexit
import random
import signal
import subprocess


#this can also be done by export PATH=$PATH:/path/to/hwoj/dir/
sys.path.append("/home/lele/Public/hwoj")
import settings
from django.core import management
management.setup_environ(settings)

from hwoj.oj.models import Problem, Submission, Judge


def signo2error(signo):
    if signo == 24:  # SIGXCPU
        return 4
    elif signo == 9:  # SIGKILL
        return 5
    else:
        #TODO
        return 6


class Daemon:
    """ the daemon class for one judge node
        start|kill|daemon
    """
    def __init__(self, name, stdin="/dev/null", stdout="/dev/null",
                stderr="/dev/null"):
        self.name = name
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = "/tmp/oj_%s.pid" % self.name

    def daemonize(self):
        """ daemonize this node """
        try:
            pid = os.fork()
            if pid > 0:
                # parent exit
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("failed to fork: %d (%s)\n" % (e.errno,
                                                        e.strerror))
            sys.exit(1)
        os.chdir("/")
        #create new session,and become the leader
        os.setsid()
        os.umask(0)

        #second fork to avoid zombies
        try:
            pid = os.fork()
            if pid > 0:
                # parent exit
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("failed to fork: %d (%s)\n" % (e.errno,
                                                        e.strerror))
            sys.exit(1)
        sys.stdout.write("judge %s(%d) is running in the background...\n"
                            % (self.name, os.getpid()))
        #redirect stds
        sys.stdout.flush()
        sys.stderr.flush()
        stdin = file(self.stdin, "r")
        stdout = file(self.stdout, "a+")
        stderr = file(self.stderr, "a+", 0)  # no buffering
        os.dup2(stdin.fileno(), sys.stdin.fileno())
        os.dup2(stdout.fileno(), sys.stdout.fileno())
        os.dup2(stderr.fileno(), sys.stderr.fileno())

        atexit.register(self.rmpid)
        file(self.pidfile, "w+").write("%d" % pid)

    def rmpid(self):
        """ remove the pid file """
        try:
            os.remove(self.pidfile)
        except:
            pass

    def pid(self):
        try:
            with open(self.pidfile, "r") as f:
                pid = int(f.read().strip())
        except:
            sys.stderr.write("failed to get pid for pid file(%s)\n"
                                % self.pidfile)
            pid = None
        return pid

    def start(self):
        """ start the daemon """
        pid = self.pid()
        if pid:
            sys.stderr.write("judge %s(%d) is already running...\n"
                            % (self.name, pid))
            sys.exit(1)
        sys.stdout.write("judge %s(%d) starts running....\n"
                            % (self.name, os.getpid()))
        self.run()

    def kill(self):
        """ kill the daemon(with caution) """
        pid = self.pid()
        if not pid:
            sys.stderr.write("failed to kill judge %s...\n" % self.name)
            sys.exit(1)
        self.rmpid()
        os.kill(pid, signal.SIGTERM)

    def restart(self):
        #TODO tenderly
        self.kill()
        self.start()

    def compile(self, submission):
        """ compile or (just skip if interpreted language) """
        sys.stdout.write("compiling...\n")
        submission.status = 1
        submission.save()

        #write src file from code field
        src = open("/tmp/%d.c" % submission.id, "w+")
        src.write(submission.code)
        src.close()
        des = "/tmp/%d" % submission.id
        try:
            os.remove(des)
        except:
            pass
        #NOTE make sure args comply with compile
        cmd = ["./compile", str(submission.language.type_id),
                os.path.abspath(src.name), os.path.abspath(des)]
        try:
            p = subprocess.Popen(cmd,
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            close_fds=True
                            )
            p.wait()
            sys.stdout.write("compile ok(%d):%s\n" % (p.returncode, cmd))
            submission.detail = p.stdout.read()
            if p.returncode < 0:
                sys.stderr.write("compliation terminated by signal:%d\n"
                                % -p.retruncode)
        except OSError, e:
            sys.stderr.write("compiling failed:%s\n" % e)

        if not os.path.exists(des):
            submission.result = 1
            submission.error = 1
            submission.save()
            return -1
        else:
            return 0

    def judge(self, submission):
        """ execute it and the compare the result """
        sys.stdout.write("judge...\n")
        submission.status = 2
        submission.save()

        problem = submission.problem

        #execute 1, files use relative path
        if not os.path.exists("/tmp/judge_%d.in" % problem.id):
            input_judge = open("/tmp/judge_%d.in" % problem.id, "w")
            input_judge.write(problem.input_judge)
            input_judge.close()
        input_judge = open("/tmp/judge_%d.in" % problem.id, "r")
        exe_cmd = ["./executor", str(submission.language.type_id),
                    str(submission.id),
                    "judge_%d.in" % problem.id, "%d.out" % submission.id,
                    str(problem.time_limit * submission.language.time_mul),
                    str(problem.memory_limit * submission.language.memory_mul)]
        try:
            p = subprocess.Popen(exe_cmd,
                            stdin=input_judge, stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            close_fds=True
                            )
            p.wait()
            retcode = p.returncode
            submission.detail = p.stdout.read()
            sys.stdout.write("executing ok(%d):%s\n" % (retcode, exe_cmd))
        except OSError, e:
            sys.stderr.write("executing failed:%s\n" % e)
            retcode = -1
        input_judge.close()
        # TODO whoese return code
        if retcode != 0:
            submission.result = 1
            submission.error = signo2error(retcode)
            submission.save()
            return retcode
        else:
            submission.detail = ""
            submission.save()
            return self.compare(submission)

    def compare(self, submission):
        # compare the output with judge output
        problem = submission.problem
        try:
            output_file = open("/tmp/%d.out" % submission.id, "r")
            output = output_file.read()
            output_file.close()
            same = (output == problem.output_judge)
            sys.stdout.write("comparing ok(%r)\n" % same)
        except OSError, e:
            sys.stderr.write("compare failed:%s" % e)
            same = False

        if same:
            submission.result = 2
            submission.error = 0
        else:
            submission.result = 1
            submission.error = 3
        submission.save()
        sys.stdout.write("judge result:%d error:%d\n" % (submission.result,
                                                    submission.error))
        return same

    def run(self):
        """ do the job """
        while 1:
            try:
                judge = Judge.objects.filter(name=self.name).get()
            except:
                sys.stdout.write("exception when fetching self:%s\n" % e)
                break
            if judge.stop_requested:
                sys.stdout.write("stop requested,stop....\n")
                judge.stop_requested = False
                judge.status = 0
                judge.save()
                break

            try:
                submission = Submission.objects.filter(locked=False,
                                                    status=0)[0]
                sys.stdout.write("fetch submission:%s\n" % submission)
            except Exception, e:
                #sys.stdout.write("exception when fetching%s\n" % e)
                sys.stdout.write("sleep for 2 seconds...\n")
                time.sleep(2)
                continue

            submission.judge = judge
            submission.locked = True
            if self.compile(submission) == 0:
                self.judge(submission)
            submission.locked = False
            submission.status = 3
            submission.save()

judge = None


def main():
    if len(sys.argv) < 2:
        print("judge [start|kill|daemon]")
    else:
        action = sys.argv[1]
        if action == "start" or action == "daemon":
            global judge
            try:
                #TODO filter more conditions
                judge = Judge.objects.filter(status=0)[0]
            except Exception, e:
                print("no judge is available:(%s)." % e)
                return

            stdin = file("/tmp/oj_%s_stdin" % judge.name, "w")
            stdin.close()
            daemon = Daemon(judge.name, stdin=stdin.name,
                            stdout="/tmp/oj_%s.stdout" % judge.name,
                            stderr="/tmp/oj_%s.stderr" % judge.name)
            signal.signal(signal.SIGTERM, handler)
            signal.signal(signal.SIGINT, handler)
            if action == "daemon":
                daemon.daemonize()
                #TODO
                judge.pid = daemon.pid()
            judge.status = 1
            judge.save()
            daemon.start()
        elif action == "stop":
            if len(sys.argv) < 3:
                print("judge stop <name-of-judge-instance>")
                return
            try:
                judge = Judge.objects.filter(name=sys.argv[2]).get()
            except Exception, e:
                print("exception when fetching self:%s" % e)
                return
            judge.stop_requested = True
            judge.save()
            print("stop request sent to judge:%s..." % judge)
        elif action == "kill":
            #daemon.kill()
            if len(sys.argv) < 3:
                print("judge kill <pid-of-instance> ...")
                return
            confirm = raw_input("Are you really sure of "
                            + "these killings(Yes,I Do/N)?")
            if confirm != "Yes,I Do":
                print("killing aborted...")
                return
            for pid in sys.argv[2:]:
                try:
                    os.kill(int(pid), signal.SIGTERM)
                except:
                    print("failed to kill instance:%s" % pid)
        else:
            print("unknow action(only start,kill,daemon is supported")


def handler(signum, frame):
    judge.stop_requested = True
    judge.save()
    print("stop request sent to judge:%s...." % judge)

if __name__ == "__main__":
    main()
